package gghilo

import (
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/gkindp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/secure/rng"
	"wzgames/internal/secure/rng/cards"
	"wzgames/internal/services/games"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

type GHilo struct {
	games.GGame

	zn   string
	cfg  *gkindp.HiLoConfig
	gift *Gift
}

func NewHilo(inst *gamep.GameInst) (games.IGameService, error) {
	hilo := &GHilo{gift: &Gift{}}
	hilo.zn = "hi-lo"
	hilo.SetGameInst(inst)
	err := hilo.updateConf()
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return hilo, err
	}
	hilo.gift.Init(hilo.cfg)
	return hilo, nil
}

func init() {
	games.RegisterGame(104, NewHilo)
	games.RegisterMarker(104, NewGMarker)
}

func (a *GHilo) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &gkindp.HiLoConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[Hilo] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

func (a *GHilo) Update() {
	err := a.updateConf()
	if err == nil {
		a.gift.Update(a.cfg)
	}
}

func (a *GHilo) Destroy() {

}

func (a *GHilo) Login(tb *games.GameTable) {
	seed, nextSeed := rng.BeginSeed(tb.Player.AgtID(), tb.Player.ID())
	tb.Marker.InitServerSeed(seed, nextSeed)
	tb.Marker.(*GMarker).SetFlagCard(a.gift.ChangeCard())

	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	a.clientInit(tb)
}

func (a *GHilo) Logout(tb *games.GameTable) {
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ != nil && betR != nil {
		if betQ.GameAct == mcomm.GameAction_GameBet && !betR.BetInfo.Save.CashOut {
			betQ.GameAct = mcomm.GameAction_GameCancel
			a.doSubmit(tb, betQ, betR)
		} else if betQ.GameAct == mcomm.GameAction_GameUpStep && betQ.ImRet != nil && betQ.ImRet.Win != mcomm.GameResult_GameLose {
			betR.BetInfo.Save.CashOut = true
			betQ.GameAct = mcomm.GameAction_GameCashOut
			betQ.ImRet.GameAct = betQ.GameAct
			betQ.ImRet.Dateline = dateutil.UnixMilli()
			a.doSubmit(tb, betQ, betR)
		}
	}
}

func (a *GHilo) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(6))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(3))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyAvatar, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteSfsArrayPre(constant.KeyFreeBets, uint16(0))
	xw.WriteSfsObjectPre(constant.KeyGameConfig, uint16(1))
	xw.WriteStringS(constant.KeyCard, tb.Marker.(*GMarker).FlagCard())

	xw.Write(a.cfg.GetConfigBin())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleArrayS(constant.KeyFastBets, tb.GameCurr.FloatValueLimit.FastBets)
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyDefaultBetValue, tb.GameCurr.FloatValueLimit.DefaultBetValue)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		xw.WriteUtfStringArray(constant.KeyFastBets, tb.GameCurr.StrValueLimit.FastBets)
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyDefaultBetValue, tb.GameCurr.StrValueLimit.DefaultBetValue)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GHilo) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerBet:
			a.betHandler(tb, m)
		case constant.HandleropenCard:
			a.openCardHandler(tb, m)
		case constant.HandlerCashOut:
			a.cashOutHandler(tb, m)
		case constant.HandlerChangeCard:
			a.changeCardHandler(tb, m)
		case constant.HandlerServerSeed:
			a.ServerSeedHandler(tb)
		case constant.HandlerGetHistory:
			a.getBetHistory(tb, m)
		case constant.HandlerRoundDetails:
			a.getRoundDetails(tb, m)
		default:
			logger.Warn("[Hilo] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	default:
		logger.Warn("[Hilo] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GHilo) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeed, tb.Marker.NextServerSeed256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GHilo) houseEdge() float64 {
	u := decimal.NewFromFloat(100.0 - a.cfg.HouseEdge).Div(calcutil.BaseCoefficient)
	v, _ := u.Float64()
	return v
}

func (a *GHilo) changeCardHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.InPlay() {
		a.RetErr(tb, response.ErrForbidden, constant.ResponseChangeCard)
		return
	}
	tb.Marker.(*GMarker).SetFlagCard(a.gift.ChangeCard())
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseChangeCard)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(1))
	xw.WriteStringS(constant.KeyCard, tb.Marker.(*GMarker).FlagCard())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GHilo) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.InPlay() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseBet)
		return
	}
	tb.Marker.SetPlay(true)
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betCard := data.GetUtfString(constant.KeyCard)
	if betCard != tb.Marker.(*GMarker).FlagCard() {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}

	betObj := tb.GenBetObj(mcomm.GameAction_GameBet)

	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseBet, true)
			return
		}
	}
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	tb.UpBetObj(betObj)
	fc := cards.String2Card(betCard)
	result := a.gift.Calculate(tb.Marker.ServerSeed(), clientSeed, 0)
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = false
	hiloBet := &statement.HiloBetInfo{
		BetInfo: &statement.HiloBetBet{
			Card:     betCard,
			FlagCard: &fc,
			Save: &statement.HiloBetSave{
				ServerSeed: tb.Marker.ServerSeed(),
				ClientSeed: clientSeed,
				Multiplier: 1.0,
				FlagCard:   &fc,
			},
		},
		Result:     result,
		TotalCards: int32(a.gift.GetTotal()),
	}
	nextSeed := rng.NextSeed(0, 0, 0)
	tb.Marker.RefreshSeed(nextSeed)
	leftCmp, lv, rightCmp, rv := a.gift.CalcNextPayout(hiloBet.BetInfo.FlagCard, a.houseEdge())
	hiloBet.BetInfo.Left = &gkindp.HiloPay{CmpType: leftCmp, Multipier: lv}
	hiloBet.BetInfo.Right = &gkindp.HiloPay{CmpType: rightCmp, Multipier: rv}
	tb.Marker.(*GMarker).SetBet(betObj, hiloBet)
	code := a.doSubmit(tb, betObj, hiloBet)
	if code != 0 {
		a.RetErrWithBala(tb, code, constant.ResponseBet, true)
		return
	}
	a.betResponse(tb, betObj, hiloBet)
}

func (a *GHilo) doSubmit(tb *games.GameTable, betObj *statement.PlayBet, hiloBet *statement.HiloBetInfo) int16 {
	switch betObj.GameAct {
	case mcomm.GameAction_GameBet:
		betObj.Detail = a.hiloBetInfo(hiloBet)
		return tb.Bet(betObj)
	case mcomm.GameAction_GameCashOut, mcomm.GameAction_GameLoseSettle:
		betObj.ImRet.Detail = a.hiloBetSave(hiloBet)
		return tb.Payout(betObj)
	case mcomm.GameAction_GameCancel:
		return tb.BetCancel(betObj)
	case mcomm.GameAction_GameUpStep:
		betObj.ImRet.Detail = a.hiloBetSave(hiloBet)
		return tb.GameUpStep(betObj)
	}

	return response.ErrInternal
}

func (a *GHilo) hiloBetInfo(hiloBet *statement.HiloBetInfo) string {
	b, err := json.Marshal(hiloBet.BetInfo)
	if err != nil {
		logger.Error("[Hilo] do submit fail on json.Marshal hilo bet", zap.Error(err))
		return ""
	}
	return byteutil.SliceByteToString(b)
}

func (a *GHilo) hiloBetSave(hiloBet *statement.HiloBetInfo) string {
	b, err := json.Marshal(hiloBet.BetInfo.Save)
	if err != nil {
		logger.Error("[Hilo] do submit fail on json.Marshal hilo bet save", zap.Error(err))
		return ""
	}
	return byteutil.SliceByteToString(b)
}

func (a *GHilo) betResponse(tb *games.GameTable, betObj *statement.PlayBet, hiloBet *statement.HiloBetInfo) {
	_bet, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(7))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, false)
	xw.WriteDoubleS(constant.KeyWinAmount, _bet)
	xw.WriteDoubleS(constant.KeyCashout, _bet)

	xw.WriteSfsObjectPre(constant.KeyNextPayout, uint16(2))
	xw.WriteSfsObjectPre(constant.KeyLeftButton, uint16(2))
	xw.WriteStringS(constant.KeyType, hiloBet.BetInfo.Left.GetCmpType())
	xw.WriteDoubleS(constant.KeyValue, calcutil.RoundFloor(hiloBet.BetInfo.Left.GetMultipier()))
	xw.WriteSfsObjectPre(constant.KeyRightButton, uint16(2))
	xw.WriteStringS(constant.KeyType, hiloBet.BetInfo.Right.GetCmpType())
	xw.WriteDoubleS(constant.KeyValue, calcutil.RoundFloor(hiloBet.BetInfo.Right.GetMultipier()))

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GHilo) calcProfit(tb *games.GameTable, precision int32, betAmt string, multiplier float64) (string, float64, string, float64, mcomm.GameResult) {
	d1, _ := decimal.NewFromString(betAmt)
	multi := decimal.NewFromFloat(multiplier)
	ret := d1.Mul(multi).Truncate(precision)
	winRet, rFloat := tb.CheckMaxWin(ret.String())
	return multi.String(), multiplier, winRet, rFloat, calcutil.CheckGameResult(multi)
}

func (a *GHilo) cashOutHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.(*GMarker).IsOpening() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseCashOut)
		return
	}
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ == nil || betR == nil || betQ.GameAct == mcomm.GameAction_GameCancel || betR.BetInfo.Save.CashOut ||
		betQ.ImRet == nil || betQ.ImRet.Win == mcomm.GameResult_GameLose {
		a.RetErr(tb, response.ErrForbidden, constant.ResponseCashOut)
		return
	}
	betR.BetInfo.Save.CashOut = true
	preAct := betQ.GameAct
	betQ.GameAct = mcomm.GameAction_GameCashOut
	multiStr, _, winStr, winAmount, gRet := a.calcProfit(tb, tb.Seat.Precision, betQ.BetAmt, betR.BetInfo.Save.Multiplier)
	betQ.ImRet.GameAct = betQ.GameAct
	betQ.ImRet.Payout = multiStr
	betQ.ImRet.WinAmt = winStr
	betQ.ImRet.Win = gRet
	betQ.ImRet.Dateline = dateutil.UnixMilli()
	code := a.doSubmit(tb, betQ, betR)
	if code != 0 {
		a.RetErr(tb, code, constant.ResponseCashOut)
		betR.BetInfo.Save.CashOut = false
		betQ.GameAct = preAct
		return
	}
	tb.Marker.SetPlay(false)

	_bet, _ := stringutil.ToFloat64(betQ.BetAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseCashOut)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(7))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBetAmount, _bet)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmount)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteDoubleS(constant.KeyCoefficientSum, calcutil.RoundFloor(betR.BetInfo.Save.Multiplier))
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betQ.ImRet.Win))

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GHilo) openCardHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.(*GMarker).CheckInOpen() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseOpenCard)
		return
	}
	defer tb.Marker.(*GMarker).ClearOpen()
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ == nil || betR == nil || (betQ.GameAct != mcomm.GameAction_GameBet && betQ.GameAct != mcomm.GameAction_GameUpStep) ||
		betR.BetInfo.Save.CashOut || betR.BetInfo.Save.CardCount >= betR.TotalCards {
		a.RetErr(tb, response.ErrForbidden, constant.ResponseOpenCard)
		return
	}
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCard)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCard)
		return
	}
	cmpType := data.GetUtfString(constant.KeyType)
	if cmpType != betR.BetInfo.Left.GetCmpType() && cmpType != betR.BetInfo.Right.GetCmpType() {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCard)
		return
	}
	betQ.GameAct = mcomm.GameAction_GameUpStep
	betQ.Dateline = dateutil.UnixMilli()
	tb.PlayInputData(betQ, exMsg)
	if betQ.ImRet == nil {
		betQ.ImRet = &statement.PlayPayout{
			BetId:   betQ.BetId,
			GameAct: betQ.GameAct,
			GameId:  betQ.GameId,
		}
	}
	if cmpType == betR.BetInfo.Left.GetCmpType() {
		a.openCardResponse(tb, betR.BetInfo.Left, betQ, betR)
	} else {
		a.openCardResponse(tb, betR.BetInfo.Right, betQ, betR)
	}
}

func (a *GHilo) openCardResponse(tb *games.GameTable, openPay *gkindp.HiloPay, betQ *statement.PlayBet, betR *statement.HiloBetInfo) {
	betQ.ImRet.Dateline = dateutil.UnixMilli()
	betR.BetInfo.Save.CardCount += 1
	var winAmt, coef float64
	var paramc uint16 = 9
	allOpened := false
	if betR.BetInfo.Save.CardCount >= betR.TotalCards {
		betR.BetInfo.Save.CardCount = betR.TotalCards
		allOpened = true
	}
	betQ.RoundId += 1
	betQ.ImRet.RoundId = betQ.RoundId
	win, openedCard := a.gift.OpenCheck(openPay, betR)
	oldMultiplier := betR.BetInfo.Save.Multiplier
	if win {
		betR.BetInfo.Save.Multiplier = openPay.GetMultipier()
		multiStr, _, winStr, winAmount, gRet := a.calcProfit(tb, tb.Seat.Precision, betQ.BetAmt, betR.BetInfo.Save.Multiplier)
		paramc = 8
		if allOpened {
			paramc = 9
			betR.BetInfo.Save.CashOut = true
		}
		betQ.ImRet.Payout = multiStr
		betQ.ImRet.WinAmt = winStr
		betQ.ImRet.Win = gRet
		winAmt = winAmount
	} else {
		betQ.ImRet.Win = mcomm.GameResult_GameLose
		betQ.ImRet.Payout = ""
		betQ.ImRet.WinAmt = "0"
	}
	betR.BetInfo.Save.Opens = append(betR.BetInfo.Save.Opens, &gkindp.HiloOpen{
		CmpType:    openPay.GetCmpType(),
		OpenCard:   openedCard,
		FlagCard:   &gamep.Card{Suite: betR.BetInfo.FlagCard.GetSuite(), Rank: betR.BetInfo.FlagCard.GetRank()},
		Win:        win,
		Multiplier: betR.BetInfo.Save.Multiplier,
	})
	leftCmp, lv, rightCmp, rv := a.gift.CalcNextPayout(openedCard, betR.BetInfo.Save.Multiplier)
	newFlagCard := cards.Card2String(openedCard)
	oldBetFlagCard := betR.BetInfo.FlagCard
	oldFlagCard := tb.Marker.(*GMarker).FlagCard()
	if win {
		betR.BetInfo.FlagCard = openedCard
		tb.Marker.(*GMarker).SetFlagCard(newFlagCard)
	}
	oldLeftCmp := betR.BetInfo.Left.CmpType
	oldLeftMultipier := betR.BetInfo.Left.Multipier
	betR.BetInfo.Left.CmpType = leftCmp
	betR.BetInfo.Left.Multipier = lv
	oldRightCmp := betR.BetInfo.Right.CmpType
	oldRightMultipier := betR.BetInfo.Right.Multipier
	betR.BetInfo.Right.CmpType = rightCmp
	betR.BetInfo.Right.Multipier = rv
	coef = calcutil.RoundFloor(betR.BetInfo.Save.Multiplier)

	preAct := betQ.GameAct
	if allOpened {
		betQ.GameAct = mcomm.GameAction_GameCashOut
		betQ.ImRet.GameAct = betQ.GameAct
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			betR.BetInfo.Save.Opens = deleteLastOpen(betR.BetInfo.Save.Opens)
			betR.BetInfo.Save.CashOut = false
			betQ.ImRet.Payout = ""
			betQ.ImRet.WinAmt = "0"
			betR.BetInfo.Save.CardCount -= 1
			betQ.RoundId -= 1
			betQ.GameAct = preAct
			betQ.ImRet.GameAct = betQ.GameAct
			betR.BetInfo.Save.Multiplier = oldMultiplier
			betR.BetInfo.FlagCard = oldBetFlagCard
			tb.Marker.(*GMarker).SetFlagCard(oldFlagCard)
			betR.BetInfo.Left.CmpType = oldLeftCmp
			betR.BetInfo.Left.Multipier = oldLeftMultipier
			betR.BetInfo.Right.CmpType = oldRightCmp
			betR.BetInfo.Right.Multipier = oldRightMultipier
			return
		}
		tb.Marker.SetPlay(false)
	} else if betQ.ImRet.Win == mcomm.GameResult_GameLose {
		betQ.GameAct = mcomm.GameAction_GameLoseSettle
		betQ.ImRet.GameAct = betQ.GameAct
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			return
		}
		tb.Marker.SetPlay(false)
	} else {
		betQ.GameAct = mcomm.GameAction_GameUpStep
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			betR.BetInfo.Save.Opens = deleteLastOpen(betR.BetInfo.Save.Opens)
			betR.BetInfo.Save.Multiplier = oldMultiplier
			betQ.RoundId -= 1
			betR.BetInfo.Save.CardCount -= 1
			betR.BetInfo.FlagCard = oldBetFlagCard
			tb.Marker.(*GMarker).SetFlagCard(oldFlagCard)
			betR.BetInfo.Left.CmpType = oldLeftCmp
			betR.BetInfo.Left.Multipier = oldLeftMultipier
			betR.BetInfo.Right.CmpType = oldRightCmp
			betR.BetInfo.Right.Multipier = oldRightMultipier
			betQ.GameAct = preAct
			return
		}
	}

	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseOpenCard)
	xw.WriteSfsObjectPre(sfs.ParamId, paramc)
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteBoolS(constant.KeyWin, win)
	xw.WriteStringS(constant.KeyType, openPay.GetCmpType())
	xw.WriteDoubleS(constant.KeyCashout, winAmt)
	xw.WriteIntS(constant.KeyCurrentCardCount, betR.BetInfo.Save.CardCount)
	xw.WriteStringS(constant.KeyCard, newFlagCard)
	xw.WriteDoubleS(constant.KeyCurrentPayout, coef)

	xw.WriteSfsObjectPre(constant.KeyNextPayout, uint16(2))
	xw.WriteSfsObjectPre(constant.KeyLeftButton, uint16(2))
	xw.WriteStringS(constant.KeyType, betR.BetInfo.Left.GetCmpType())
	xw.WriteDoubleS(constant.KeyValue, calcutil.RoundFloor(betR.BetInfo.Left.GetMultipier()))
	xw.WriteSfsObjectPre(constant.KeyRightButton, uint16(2))
	xw.WriteStringS(constant.KeyType, betR.BetInfo.Right.GetCmpType())
	xw.WriteDoubleS(constant.KeyValue, calcutil.RoundFloor(betR.BetInfo.Right.GetMultipier()))

	if !win || allOpened {
		if !win {
			coef = 0
		}
		_bet, _ := stringutil.ToFloat64(betQ.BetAmt, 0)
		xw.WriteSfsObjectPre(constant.KeyEndGame, uint16(7))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteDoubleS(constant.KeyBetAmount, _bet)
		xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
		xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
		xw.WriteDoubleS(constant.KeyCoefficientSum, coef)
		xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
		xw.WriteBoolS(constant.KeyWin, win)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func deleteLastOpen(a []*gkindp.HiloOpen) []*gkindp.HiloOpen {
	newOne := a[:0]
	l := len(a)
	for i := 0; i < l-1; i++ {
		newOne = append(newOne, a[i])
	}

	return newOne
}

func (a *GHilo) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}

func (a *GHilo) getRoundDetails(tb *games.GameTable, m *sfs.TSfsObject) {
	data := m.GetContent()
	if data == nil {
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		return
	}
	betId, _ := stringutil.ToInt64(data.GetUtfString(constant.KeyRoundId), 0)
	if betId <= 0 {
		return
	}

	ret, code := tb.GetUserBetDetail(betId)
	if code != 0 {
		return
	}

	hiloBet := &statement.HiloBetSave{}
	err := json.Unmarshal(byteutil.StringToSliceByte(ret.Detail), hiloBet)
	if err != nil {
		logger.Error("[Hilo] bet fail on json.Unmarshal hilo bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return
	}

	betAmt, _ := stringutil.ToFloat64(ret.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(ret.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseRoundDetails)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(12))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteLongS(constant.KeyBetId, betId)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteLongS(constant.KeyTimeRound, ret.BetTime)
	xw.WriteDoubleS(constant.KeyPayout, calcutil.RoundFloor(hiloBet.Multiplier))
	xw.WriteStringS(constant.KeyClientSeed, hiloBet.ClientSeed)
	xw.WriteStringS(constant.KeyCurrency, ret.Curr)
	xw.WriteBoolS(constant.KeyIsFreeBetGame, ret.FreeBet)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(ret.Win))
	xw.WriteStringS(constant.KeyServerSeed, hiloBet.ServerSeed)

	xw.WriteSfsObjectPre(constant.KeyDetails, uint16(3))
	xw.WriteDoubleS(constant.KeySumCoef, calcutil.RoundFloor(hiloBet.Multiplier))
	xw.WriteStringS(constant.KeyFlagCard, cards.Card2String(hiloBet.FlagCard))
	lopens := len(hiloBet.Opens)
	xw.WriteSfsArrayPre(constant.KeyResult, uint16(lopens))
	for _, op := range hiloBet.Opens {
		xw.WriteUByte(sfs.SfsObject)
		var n uint16
		if op.Win {
			n = 2
		} else {
			n = 1
		}
		xw.WriteUShort(n)
		xw.WriteStringS(constant.KeyCard, cards.Card2String(op.GetOpenCard()))
		if op.Win {
			xw.WriteStringS(constant.KeyType, op.GetCmpType())
		}
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}
